package com.sopaths.crawler.thread;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

import javax.inject.Named;

import org.apache.log4j.Logger;

import com.sopaths.contract.crawler.CrawlerTaskSource;
import com.sopaths.contract.crawler.CrawlerTaskType;
import com.sopaths.crawler.Crawler;
import com.sopaths.crawler.tasks.CrawlerTask;

@Named
public class CrawlerTaskExecutor {

	private static Logger logger = Logger.getLogger(CrawlerTaskExecutor.class);

	private BlockingQueue<CrawlerTask<?>> queue;

	private AtomicInteger totalTask;

	private CrawlerTaskResultHandlerDispatcher handlerDispatcher;

	private Map<CrawlerTaskType, Map<CrawlerTaskSource, Crawler>> crawlerMap;

	private int crawlerThreadCount;

	private String threadName;

	private int queueSize;

	public void prepare() {
		queue = new LinkedBlockingQueue<>(queueSize);
		for (int i = 0; i < crawlerThreadCount; i++) {
			Thread thread = new Thread(new CrawlerTaskExecuteThread(crawlerMap));
			thread.setName(threadName + "-" + i);
			thread.start();
		}
	}

	public void submit(CrawlerTask<?> task) {
		try {
			String serial = null;
			if (logger.isInfoEnabled()) {
				serial = UUID.randomUUID().toString();
				logger.info("Submit task: " + task.getClass() + ". UUID: "
						+ serial);
			}
			queue.put(task);
			if (logger.isDebugEnabled()) {
				logger.debug("Submit task successfully. Queue Size: "
						+ queue.size() + ". UUID: " + serial);
			}
		} catch (InterruptedException ex) {
			logger.error("An error occurred while submiting task.", ex);
		}
	}

	/**
	 * 暂时没有使用这个方法，这个方法会记录准备提交的task的数量，用于显示进度条，但是会给Handler产生内存问题，因为Handler必须要缓存生成Task
	 * @param tasks
	 */
	public void submit(List<CrawlerTask<?>> tasks) {
		totalTask.addAndGet(tasks.size());
		for (CrawlerTask<?> task : tasks) {
			submit(task);
		}
	}

	private class CrawlerTaskExecuteThread implements Runnable {
		private Logger logger = Logger
				.getLogger(CrawlerTaskExecuteThread.class);

		private Map<CrawlerTaskType, Map<CrawlerTaskSource, Crawler>> crawlerMap;

		public CrawlerTaskExecuteThread(
				Map<CrawlerTaskType, Map<CrawlerTaskSource, Crawler>> crawlerMap) {
			this.crawlerMap = crawlerMap;
		}

		@Override
		public void run() {
			while (true) {
				try {
					logger.info("Take task from queue.");
					CrawlerTask<?> task = queue.take();
					if (logger.isDebugEnabled()) {
						logger.debug("Take task from queue successfully. Task: "
								+ task.getClass()
								+ ". Type: "
								+ task.getType()
								+ ". Source: "
								+ task.getSource()
								+ ". Queue size: " + queue.size());
					}
					Crawler crawler = crawlerMap.get(task.getType()).get(
							task.getSource());
					if (crawler != null) {
						crawler.crawl(task);
						if (task.isFinished()) {
							handlerDispatcher.Handle(task);
						} else {
							logger.error("The task has not been executed properly. Task: " + task);
						}
					} else {
						logger.warn("Unknown task type or source: "
								+ task.getType() + "-" + task.getSource());
					}
				} catch (InterruptedException ex) {
					logger.error("An error occurred while executing task.", ex);
				}
			}
		}
	}

	public int getRemainTasks() {
		return queue.size();
	}

	public void setHandlerDispatcher(
			CrawlerTaskResultHandlerDispatcher handlerDispatcher) {
		this.handlerDispatcher = handlerDispatcher;
	}

	public void setCrawlerThreadCount(int crawlerThreadCount) {
		this.crawlerThreadCount = crawlerThreadCount;
	}

	public void setThreadName(String threadName) {
		this.threadName = threadName;
	}

	public int getQueueSize() {
		return queueSize;
	}

	public void setQueueSize(int queueSize) {
		this.queueSize = queueSize;
	}

	public void setCrawlerMap(
			Map<CrawlerTaskType, Map<CrawlerTaskSource, Crawler>> crawlerMap) {
		this.crawlerMap = crawlerMap;
	}

}
